package controllers

import java.io.File
import java.nio.file.Files

import actor.MissionManageActor
import akka.actor.AbstractActor.Receive
import akka.actor.{Actor, ActorSystem, PoisonPill, Props}
import akka.stream.Materializer
import command.{CommandUtils, MissionUtils}
import dao._
import javax.inject.{Inject, Singleton}
import org.apache.commons.lang3.StringUtils
import play.api.libs.json.{JsValue, Json}
import play.api.mvc.{AbstractController, ControllerComponents, WebSocket}

import scala.concurrent.ExecutionContext.Implicits.global
import models.Tables._
import org.apache.commons.io.FileUtils
import org.joda.time.DateTime
import org.zeroturnaround.zip.ZipUtil
import play.api.libs.streams.ActorFlow
import play.api.libs.ws.WSClient
import tool.Pojo._

import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.duration._
import implicits.Implicits._
import tool.{FileTool, FormTool, Tool, WebTool}
import utils.Utils

import scala.language.postfixOps
import FormTool._
import cats.data.Validated
import shared.VarTool

/**
 * Created by Administrator on 2019/8/8
 */
@Singleton
class MissionController @Inject()(cc: ControllerComponents, formTool: FormTool
                                 )(implicit val system: ActorSystem,
                                   implicit val modeDao: ModeDao,
                                   implicit val missionDao: MissionDao,
                                   implicit val sampleDao: SampleDao,
                                   implicit val userDao: UserDao,
                                   implicit val configDao: ConfigDao,
                                   implicit val materializer: Materializer,
                                   implicit val posSiteDao: PosSiteDao,
                                   implicit val stepMissionDao: StepMissionDao
                                 ) extends
  AbstractController(cc) {

  implicit val dao = MyDao(missionDao, sampleDao, configDao, stepMissionDao)

  val missionManageActor = system.actorOf(
    Props(new MissionManageActor())
  )
  missionManageActor ! "ask"

  def newMissionBefore = Action { implicit request =>
    val missionName = s"${Tool.generateMissionName}"
    val data = formTool.idOpForm.bindFromRequest().get
    Ok(views.html.user.newMission(missionName, data.id))
  }

  def missionNameCheck = Action.async { implicit request =>
    val data = formTool.missionNameForm.bindFromRequest.get
    val userId = Tool.getUserId
    missionDao.selectOptionByMissionName(userId, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(Json.obj("valid" -> false))
        case None =>
          Ok(Json.obj("valid" -> true))
      }
    }
  }

  def newMission = Action.async(parse.multipartFormData) { implicit request =>
    val data = FormTool.missionForm.bindFromRequest().get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val myTmpDir = Tool.getDataDir(tmpDir)
    val userId = Tool.getUserId
    val row = MissionRow(0, s"${data.missionName}", "", userId, VarTool.singleMissionStr, new DateTime(), None, "preparing")
    missionDao.insert(row).flatMap(_ => missionDao.selectByMissionName(row.userId, row.missionName)).flatMap { mission =>
      val outDir = WebTool.getUserMissionDir
      val missionDir = MissionUtils.getMissionDir(mission.id, outDir)
      val (workspaceDir, resultDir) = (missionDir.workspaceDir, missionDir.resultDir)
      val dataDir = new File(workspaceDir.getParent, "data")
      FileUtils.copyDirectory(myTmpDir.tmpDir, dataDir)
      myTmpDir.fqFile.copyTo(Tool.getInputTxtFile(workspaceDir))
      val configFile = Tool.produceConfigFile(data, workspaceDir)
      Tool.productInputXmlFile(workspaceDir, data)
      Tool.deleteDirectory(myTmpDir.tmpDir)
      val newMission = mission.copy(state = "wait")
      missionDao.update(newMission).map { x =>
        Ok(Json.obj("valid" -> true))
      }
    }
  }

  def getChildrenNodes = Action.async { implicit request =>
    configDao.selectGzPath.map { gzPath =>
      val gzDir = new File(gzPath)
      val relativePath = FormTool.nodeForm.bindFromRequest().fold(e => "", s => s.id)
      val dir = new File(gzDir, relativePath)
      val nodes = dir.myListFiles.map { x =>
        if (x.isDirectory) {
          val relativePath = Utils.getRelativePath(gzDir, x)
          val pRelativePath = Utils.getRelativePath(gzDir, x.getParentFile)
          Map("id" -> relativePath, "pId" -> pRelativePath, "name" -> x.getName,
            "icon" -> "/assets/images/baidu_folder.png", "isParent" -> x.isParent.toString)
        } else Map[String, String]()
      }.filter(!_.isEmpty)
      Ok(Json.toJson(nodes))
    }
  }

  def getTreeNodes = Action { implicit request =>
    val gzDir = new File(Utils.execFuture(configDao.selectGzPath))
    val splitDir = FormTool.serverDirForm.bindFromRequest().fold(error => "", success => success.splitDir)
    val curDir = new File(gzDir, splitDir)

    def loop(acc: List[Map[String, String]], files: List[File]): List[Map[String, String]] = {
      files match {
        case Nil => acc
        case x :: xs if x.isDirectory =>
          val b = x.getParentFile != null && x.getAbsolutePath != gzDir.getAbsolutePath
          val nodes = if (b) {
            x.getParentFile.myListFiles.filter(_.isDirectory).map { y =>
              val relativePath = Utils.getRelativePath(gzDir, y)
              val pRelativePath = Utils.getRelativePath(gzDir, y.getParentFile)
              Map("id" -> relativePath, "pId" -> pRelativePath, "name" -> y.getName,
                "icon" -> "/assets/images/baidu_folder.png", "isParent" -> y.isParent.toString)
            }
          } else List[Map[String, String]]()
          val list = if (b) List(x.getParentFile) else List[File]()
          loop(nodes ::: acc, xs ::: list)
        case x :: xs =>
          loop(acc, xs)
      }
    }

    val dbNodes = loop(List[Map[String, String]](), List(curDir)).filter(!_.isEmpty)
    Ok(Json.toJson(dbNodes))
  }

  def getFiles = Action.async { implicit request =>
    val data = FormTool.serverDirForm.bindFromRequest().get
    configDao.selectGzPath.map { gzPath =>
      val gzDir = new File(gzPath)
      val splitDir = new File(gzDir, data.splitDir)
      val maps = if (data.splitDir.nonEmpty) {
        splitDir.myListFiles.filter(_.isFile).map { file =>
          Map("id" -> file.getAbsolutePath, "name" -> file.getName, "isParent" -> "false")
        }
      } else List[Map[String, String]]()
      Ok(Json.toJson(maps))
    }
  }

  def newBatchMission = Action.async(parse.multipartFormData) { implicit request =>
    val data = FormTool.missionNameForm.bindFromRequest().get
    val tmpBatchData = FormTool.batchMissionForm.bindFromRequest().get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val sampleInfoFile = Tool.fileMoveDir("sampleInfoFile", tmpDir)
    val userId = Tool.getUserId
    val dbIds = Utils.execFuture(sampleDao.selectAllId(userId)).toList
    val isCover = tmpBatchData.isCover
    val sampleInfoFileCheckData = SampleInfoFileCheckData(dbIds, isCover)
    val gzPath = Utils.execFuture(configDao.selectGzPath)
    val myMessage = Tool.dirCheck(gzPath, tmpBatchData).andThen { b =>
      FileTool.fileCheck(sampleInfoFile, sampleInfoFileCheckData)
    }.toMyMessage
    if (myMessage.valid) {
      val gzDir = new File(gzPath)
      val splitDir = new File(gzDir, tmpBatchData.splitDir)
      val batchMissionData = tmpBatchData.copy(splitDir = splitDir.getAbsolutePath)
      val tmpDir = Tool.createTempDirectory("tmpDir")
      val indexFile = new File(Tool.dataDir, "index.txt")
      val row = MissionRow(0, s"${data.missionName}", batchMissionData.missionIntro, userId, VarTool.batchMissionStr, new DateTime(), None, "preparing")
      missionDao.insert(row).flatMap(_ => missionDao.selectByMissionName(row.userId, row.missionName)).flatMap { mission =>
        val outDir = WebTool.getUserMissionDir
        val missionDir = MissionUtils.getMissionDir(mission.id, outDir)
        val (workspaceDir, resultDir) = (missionDir.workspaceDir, missionDir.resultDir)
        indexFile.copyTo(Tool.getIndexFile(workspaceDir))
        sampleInfoFile.copyTo(Tool.getSampleInfoFile(workspaceDir))
        Tool.deleteDirectory(tmpDir)
        println("come in")
        Utils.t2ConfigFile(workspaceDir, batchMissionData)
        val dir = new File("C:\\forensic_database\\test")
        val newMission = mission.copy(state = "wait")
        missionDao.update(newMission).map { x =>
          Ok(Json.obj("valid" -> true))
        }
      }
    } else {
      Tool.deleteDirectory(tmpDir)
      Future.successful(Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message)))
    }
  }

  def getAllMission = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.kindForm.bindFromRequest().get
    missionDao.selectAll(userId, data.kind).map { x =>
      Future {
        val missionIds = x.map(_.id.toString)
        val missionDir = WebTool.getUserMissionDir
        missionDir.listFiles().filter {
          dir =>
            !missionIds.contains(dir.getName)
        }.foreach(Utils.deleteDirectory(_))
      }
      val array = Utils.getArrayByTs(x)
      Ok(Json.toJson(array))
    }
  }

  def downloadData = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      val missionId = data.missionId
      missionDao.selectByMissionId(missionId).map {
        mission =>
          val missionIdDir = WebTool.getMissionIdDir(missionId)
          val dataDir = new File(missionIdDir, "data")
          val dataFile = new File(missionIdDir, s"data.zip")
          if (!dataFile.exists()) ZipUtil.pack(dataDir, dataFile)
          Ok.sendFile(dataFile).withHeaders(
            CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${mission.missionName}_data.zip"),
            CONTENT_TYPE -> "application/x-download"
          )
      }
  }

  def deleteMissionById = Action.async {
    implicit request =>
      val data = FormTool.missionIdForm.bindFromRequest().get
      val userId = Tool.getUserId
      missionDao.selectByMissionId(data.missionId).flatMap { mission =>
        Future {
          val workspaceDir = WebTool.getMissionWorkspaceDirById(data.missionId)
          CommandUtils.killPid(workspaceDir)
          val missionIdDir = Tool.getMissionIdDir(mission)
          Utils.deleteDirectory(missionIdDir)
        }
        missionDao.deleteById(userId, data.missionId).map { x =>
          Ok(Json.toJson("success"))
        }
      }
  }

  def updateMissionSocket(kind: Option[String]) = WebSocket.accept[JsValue, JsValue] { implicit request =>
    val userId = Tool.getUserId
    case class MissionAction(beforeMissions: Seq[MissionRow], action: String)
    ActorFlow.actorRef(out => Props(new Actor {
      override def receive: Receive = {
        case msg: JsValue if (msg \ "info").as[String] == "start" =>
          val beforeMissions = Utils.execFuture(missionDao.selectAll(userId, kind))
          out ! Utils.getJsonByTs(beforeMissions)
          system.scheduler.scheduleOnce(3 seconds, self, MissionAction(beforeMissions, "update"))
        case MissionAction(beforeMissions, action) =>
          missionDao.selectAll(userId, kind).map {
            missions =>
              val currentMissions = missions
              if (currentMissions.size != beforeMissions.size) {
                out ! Utils.getJsonByTs(currentMissions)
              } else {
                val b = currentMissions.zip(beforeMissions).forall {
                  case (currentMission, beforeMission) =>
                    currentMission.id == beforeMission.id && currentMission.state == beforeMission.state
                }
                if (!b) {
                  out ! Utils.getJsonByTs(currentMissions)
                }
              }
              system.scheduler.scheduleOnce(3 seconds, self, MissionAction(currentMissions, "update"))
          }
        case _ =>
          self ! PoisonPill
      }

      override def postStop(): Unit = {
        self ! PoisonPill
      }
    }))
  }

  def getLogContent = Action.async {
    implicit request =>
      val userId = Tool.getUserId
      val data = FormTool.missionIdForm.bindFromRequest().get
      missionDao.selectByMissionId(data.missionId).map {
        mission =>
          val missionIdDir = WebTool.getMissionIdDir(data.missionId)
          val logFile = new File(missionIdDir, s"log.txt")
          val logStr = FileUtils.readFileToString(logFile, "UTF-8")
          Ok(Json.obj("str"->logStr))
      }
  }

  def missionSummaryBefore = Action { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    Ok(views.html.user.missionSummary(data.missionId))
  }

  def missionSummaryBeforeByName = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionNameForm.bindFromRequest().get
    missionDao.selectByMissionName(userId, data.missionName).map { x =>
      Ok(views.html.user.missionSummary(x.id))
    }
  }

  def getMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    missionDao.selectByMissionId(data.missionId).map { x =>
      val json = Utils.getJsonByT(x)
      Ok(Json.toJson(json))
    }
  }

  def getSampleStatInfo = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.selectByMissionId(data.missionId).flatMap { mission =>
      sampleDao.selectAll(userId, mission.missionName).map { samples =>
        val posNum = samples.filter(_.kind == VarTool.posStr).size
        val negNum = samples.filter(_.kind == VarTool.negStr).size
        val emptyNum = samples.filter { sample =>
          sample.kind == VarTool.emptyStr || sample.kind.isEmpty
        }.size
        Ok(Json.obj("posNum" -> posNum.toString, "negNum" -> negNum.toString, "emptyNum" -> emptyNum.toString))
      }
    }
  }

  def getPosSamples = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.selectByMissionId(data.missionId).flatMap { mission =>
      sampleDao.selectAllPos(userId, mission.missionName).map { samples =>
        val samplesJson = samples.map { sample =>
          Tool.getJsonBySample(sample)
        }
        val json = Json.obj("samples" -> samplesJson)
        Ok(json)
      }
    }
  }

  def search = Action.async { implicit request =>
    val data = FormTool.searchForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.selectAll(userId).map { missions =>
      val rsMissions = missions.filter { mission =>
        val endB = mission.endTime match {
          case Some(endTime) => data.endDate.map(y => endTime.isBefore(y)).getOrElse(true)
          case None => true
        }
        val introB = data.missionIntro match {
          case Some(missionIntro) => mission.missionIntro.contains(missionIntro)
          case None => true
        }
        val missionNameB = data.missionName match {
          case Some(missionName) => mission.missionName.contains(missionName)
          case None => true
        }
        missionNameB && introB &&
          data.startDate.map(mission.startTime.isAfter(_)).getOrElse(true) &&
          endB
      }
      val json = Utils.getJsonByTs(rsMissions)
      Ok(json)
    }
  }

  def getNegSamples = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.selectByMissionId(data.missionId).flatMap { mission =>
      sampleDao.selectAllNeg(userId, mission.missionName).map { samples =>
        val samplesJson = samples.map { sample =>
          Tool.getJsonBySample(sample)
        }
        val json = Json.obj("samples" -> samplesJson)
        Ok(json)
      }
    }
  }

  def getNormalSampleInfo = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    missionDao.selectByMissionId(data.missionId).flatMap { mission =>
      sampleDao.selectAllNormal(userId, mission.missionName).map { samples =>
        val datas = samples.toList.map { sample =>
          val sampleIdDir = Tool.getSampleIdDir(sample)
          val basicFile = Tool.getBasicFile(sampleIdDir)
          basicFile.txtLines.lineMapNoLower.map(_ ("Available Depth")).head.toDouble
        }
        val cv = Utils.cv(datas)
        val jsons = samples.zip(datas).map { case (sample, data) =>
          Map("id" -> sample.id, "value" -> data.toString)
        }
        Ok(Json.obj("samples" -> jsons, "cv" -> cv.toString))
      }
    }
  }

}
